/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package io.milton.http;

import java.io.OutputStream;
import java.util.Date;
import java.util.List;
import java.util.Map;

public interface Response {

    String HTTP = "text/html";
    String IMAGE_JPG = "image/jpg";
    String MULTIPART = "multipart/form-data";
    String MULTIPART_MIXED = "multipart/mixed";
    String XML = "text/xml; charset=UTF-8";
    String APPLICATION_XML = "application/xml; charset=utf-8";

    enum ContentType {

        HTTP,
        MULTIPART,
        MULTIPART_MIXED,
        IMAGE_JPG,
        XML
    }

    enum ContentEncoding {

        GZIP("gzip");
        public final String code;

        ContentEncoding(String code) {
            this.code = code;
        }
    }

    enum Header {

        CACHE_CONTROL("Cache-Control"),
        WWW_AUTHENTICATE("WWW-Authenticate"),
        CONTENT_LENGTH("Content-Length"),
        CONTENT_TYPE("Content-Type"),
        CONTENT_ENCODING("Content-Encoding"),
        LOCATION("Location"),
        ALLOW("Allow"),
        DAV("DAV"),
        DATE("Date"),  // was all-caps , eg DATE, which is wrong
        LAST_MODIFIED("Last-Modified"),
        LOCK_TOKEN("Lock-Token"),
        EXPIRES("Expires"),
        ETAG("ETag"),
        VARY("Vary"),
        ACCESS_CONTROL_ALLOW_ORIGIN("Access-Control-Allow-Origin"),
        ACCEPT_RANGES("Accept-Ranges"),
        CONTENT_RANGE("Content-Range");

        public final String code;

        Header(String code) {
            this.code = code;
        }
    }

    enum CacheControlResponse {

        PUBLIC("public"),
        PRIVATE("private"), // [ "=" <"> 1#field-name <"> ] ; Section 14.9.1
        NO_CACHE("no-cache"), // [ "=" <"> 1#field-name <"> ]; Section 14.9.1
        NO_STORE("no-store"), //                             ; Section 14.9.2
        NO_TRANSFORM("no-transform"), //                         ; Section 14.9.5
        MUST_REVALIDATE("must-revalidate"), //                     ; Section 14.9.4
        PROXY_REVALIDATE("proxy-revalidate"), //                   ; Section 14.9.4
        MAX_AGE("max-age"), // "=" delta-seconds            ; Section 14.9.3
        S_MAX_AGE("s-maxage"), // "=" delta-seconds           ; Section 14.9.3
        CACHE_EXT("cache-extension");  //                       ; Section 14.9.6
        public final String code;

        CacheControlResponse(String code) {
            this.code = code;
        }
    }

    enum Status {

        SC_OK(ResponseStatus.SC_OK, "OK"),
        SC_CREATED(ResponseStatus.SC_CREATED),
        SC_ACCEPTED(ResponseStatus.SC_ACCEPTED),
        SC_NO_CONTENT(ResponseStatus.SC_NO_CONTENT),
        SC_MULTI_STATUS(207, "Multi-Status"),
        SC_MOVED_PERMANENTLY(ResponseStatus.SC_MOVED_PERMANENTLY),
        SC_MOVED_TEMPORARILY(ResponseStatus.SC_MOVED_TEMPORARILY),
        SC_TEMPORARY_REDIRECT(ResponseStatus.SC_TEMPORARY_REDIRECT),
        SC_NOT_MODIFIED(ResponseStatus.SC_NOT_MODIFIED),
        SC_BAD_REQUEST(ResponseStatus.SC_BAD_REQUEST),
        SC_UNAUTHORIZED(ResponseStatus.SC_UNAUTHORIZED),
        SC_FORBIDDEN(ResponseStatus.SC_FORBIDDEN),
        SC_NOT_FOUND(ResponseStatus.SC_NOT_FOUND, "Not Found"),
        SC_INTERNAL_SERVER_ERROR(ResponseStatus.SC_INTERNAL_SERVER_ERROR),
        SC_NOT_IMPLEMENTED(ResponseStatus.SC_NOT_IMPLEMENTED),
        SC_BAD_GATEWAY(ResponseStatus.SC_BAD_GATEWAY),
        SC_SERVICE_UNAVAILABLE(ResponseStatus.SC_SERVICE_UNAVAILABLE),
        SC_PARTIAL_CONTENT(ResponseStatus.SC_PARTIAL_CONTENT),
        SC_CONTINUE(100),
        SC_METHOD_NOT_ALLOWED(405),
        SC_CONFLICT(409),
        SC_PRECONDITION_FAILED(412),
        SC_REQUEST_TOO_LONG(413),
        SC_UNSUPPORTED_MEDIA_TYPE(415),
        SC_EXPECTATION_FAILED(ResponseStatus.SC_EXPECTATION_FAILED), // 417
        SC_UNPROCESSABLE_ENTITY(418),
        SC_INSUFFICIENT_STORAGE(507),
        SC_METHOD_FAILURE(420),
        SC_LOCKED(423);
        public final int code;
        public final String text;

        Status(int code, String text) {
            this.code = code;
            this.text = text;
        }

        Status(int code) {
            this.code = code;
            this.text = null;
        }

        @Override
        public String toString() {
            if (text != null) {
                return "HTTP/1.1 " + code + " " + text;
            } else {
                return "HTTP/1.1 " + code;
            }
        }

        public static Status fromCode(int i) {
            for (Status s : values()) {
                if (s.code == i) return s;
            }
            return null;
        }
    }

    interface Entity {
        void write(Response response, OutputStream outputStream) throws Exception;
    }

    Response.Status getStatus();

    Map<String, String> getHeaders();

    /**
     * @return - the content length which might have been set by a handler, or null
     * if none has been set
     */
    Long getContentLength();

    void setContentEncodingHeader(ContentEncoding encoding);

    void setExpiresHeader(Date expiresAt);

    void setLockTokenHeader(String tokenId);

    /**
     * Must set multiple Authenticate headers, one for each challenge
     * <p>
     * This will usually be one each for basic, digest, ntlm, etc
     * <p>
     * Note that order might be significant to some clients, so should be
     * preserved. ie list item zero should be first.
     *
     * @param challenges - a list of http authentication challenges
     */
    void setAuthenticateHeader(List<String> challenges);

    void setStatus(Status status);

    void setEtag(String uniqueId);

    void setContentRangeHeader(long start, long finish, Long totalLength);

    void setContentLengthHeader(Long totalLength);

    void setContentTypeHeader(String string);

    String getContentTypeHeader();

    Entity getEntity();

    void setEntity(Entity entity);

    /**
     * Set the cache control header to allow the resource to be cached
     * for the given number of seconds
     *
     * @param deltaSeconds - must not be null
     */
    void setCacheControlMaxAgeHeader(Long deltaSeconds);

    void setCacheControlPrivateMaxAgeHeader(Long deltaSeconds);

    /**
     * Set the cache control header to indicate that the resource should not
     * be cached
     */
    void setCacheControlNoCacheHeader();

    void setLastModifiedHeader(Date date);

    void setDavHeader(String string);

    void setNonStandardHeader(String code, String value);

    String getNonStandardHeader(String code);

    void setAllowHeader(List<String> methodsAllowed);

    OutputStream getOutputStream();

    void setLocationHeader(String redirectUrl);

    /**
     * Sets the Vary response header, necessary for response compression
     *
     * @param string
     */
    void setVaryHeader(String string);

    void setDateHeader(Date date);

    /**
     * Used for CORS responses
     *
     * @return
     */
    String getAccessControlAllowOrigin();

    void setAccessControlAllowOrigin(String s);

    String getAcceptRanges();

    void setAcceptRanges(String s);

    /**
     * Called to indicate that the request is completed. Some response implementations
     * might choose to close the http connection, while others which implement pipelining
     * might not
     */
    void close();

    /**
     * Called by milton when an error occurs. The underlying HTTP provider should
     * choose to close the HTTP connection, because the amount of data already sent
     * might not be consistent with the content length header which may have already
     * been sent
     *
     * @param status
     * @param message
     */
    void sendError(Status status, String message);

    /**
     * Will set the status to moved_temporarily and set the location header
     * to the given url
     *
     * @param url
     */
    void sendRedirect(String url);

    /**
     * Will set the status to moved_permanently and set the location header
     * to the given url
     *
     * @param url
     */
    void sendPermanentRedirect(String url);

    /**
     * The <code>setCookie</code> method is used to set a cookie value
     * with the cookie name. This will add a cookie to the response
     * stored under the name of the cookie, when this is committed it
     * will be added as a Set-Cookie header to the resulting response.
     *
     * @param cookie this is the cookie to be added to the response
     * @return returns the cookie that has been set in the response
     */
    Cookie setCookie(Cookie cookie);

    /**
     * The <code>setCookie</code> method is used to set a cookie value
     * with the cookie name. This will add a cookie to the response
     * stored under the name of the cookie, when this is committed it
     * will be added as a Set-Cookie header to the resulting response.
     * This is a convenience method that avoids cookie creation.
     *
     * @param name  this is the cookie to be added to the response
     * @param value this is the cookie value that is to be used
     * @return returns the cookie that has been set in the response
     */
    Cookie setCookie(String name, String value);
}
